package uk.co.q3c.deplan.server.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.EnumSet;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;

import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.XMLEvent;

import org.apache.log4j.Logger;

import uk.co.q3c.deplan.client.domain.dag.ChangeInvalidException;
import uk.co.q3c.deplan.client.domain.dag.CycleDetectedException;
import uk.co.q3c.deplan.client.domain.resource.BaseCalendar;
import uk.co.q3c.deplan.client.domain.resource.IndividualResource;
import uk.co.q3c.deplan.client.domain.resource.PersistenceReferenceException;
import uk.co.q3c.deplan.client.domain.resource.Resource;
import uk.co.q3c.deplan.client.domain.resource.ResourceGroup;
import uk.co.q3c.deplan.client.domain.resource.ResourcePool;
import uk.co.q3c.deplan.client.domain.resource.WorkingCalendar;
import uk.co.q3c.deplan.client.domain.task.BaseTask;
import uk.co.q3c.deplan.client.domain.task.Task;
import uk.co.q3c.deplan.client.service.ServiceFactory;

/**
 * @inject {@link BaseCalendar}
 * @inject {@link #resourcePool}
 * @inject {@link #defaultResourceName}
 * @inject {@link #source}
 * @author DSowerby 6 Jun 2008 19:44:17
 * 
 */
public class XMLImport {
	ServiceFactory connection;
	transient protected final Logger logger = Logger.getLogger(getClass().getName());

	enum Tag {
		ActualStart,
		Assignments,
		Assignment,
		Project,
		PredecessorLink,
		PredecessorUID,
		Tasks,
		Task,
		Resources,
		Resource,
		Name,
		Calendars,
		_End,
		_Unused,
		UID,
		Priority,
		Work,
		RemainingWork,
		ActualFinish,
		StatusManager,
		Type,
		Units,
		TaskUID,
		ResourceUID,
		OutlineLevel,
		Notes,
		Duration

	}

	// Up means up towards the top of the tree - the opposite way to outloine
	// level numbering
	enum LevelChange {
		UP,
		DOWN,
		SAME
	}

	public class ImportResults {
		public String source;
		public int projects;
		public int tasks;
		public int newResources;
		public int matchedResources;
		public int newResourceGroups;
		public int noResource;
		public Date start;
		public Date finish;
		public int summaryTasks;
		public int taskUIDNotFound;
		public int predecessorNotFound;
	}

	public ImportResults importResults = new ImportResults();
	Stack<Tag> tagStack = new Stack<Tag>();
	boolean onEndTag = false;
	protected File databaseFile;

	BaseTask currentProject;
	BaseCalendar baseCalendar;
	/**
	 * used in call to {@link WorkingCalendar#minutesToWorkingDays} when calculating duration
	 */
	private final Calendar fixedMonday;

	public XMLImport() {
		fixedMonday = new GregorianCalendar();
		fixedMonday.set(2008, 7, 4, 0, 0, 0);
	}

	/**
	 * If the {@link #baseCalendar} has been set, then it is returned, otherwise the one from the database, or failing that, a newly created
	 * one.
	 * 
	 * @return
	 */
	public BaseCalendar getBaseCalendar() {
		if (baseCalendar != null) {
			return baseCalendar;
		}
		try {
			throw new RuntimeException("Replace Model.getDbc() in XMLIMport.getBaseCalendar()");
			// baseCalendar = Model.getDbc().getBaseCalendarDao().find();
			// if (baseCalendar != null) {
			// return baseCalendar;
			// }
		} catch (Exception e) {
			logger.info(e.getMessage());
		}
		baseCalendar = new BaseCalendar(connection);
		// baseCalendar.setWorkingTime(new DefaultWorkingTime());
		return baseCalendar;

	}

	public void setBaseCalendar(BaseCalendar baseCalendar) {
		this.baseCalendar = baseCalendar;
	}

	class PredecessorReference {
		Task task;
		String referenceUID;
	}

	class Assignment {
		String resourceUID;
		double units;
		String taskUID;

	}

	class TaskOutline {

		Task task;
		int outlineLevel;

		public TaskOutline(Task task, int outlineLevel) {
			super();
			this.task = task;
			this.outlineLevel = outlineLevel;
		}

	}

	// transient protected final Log Log = Log.getLog(getClass().getName());
	// transient protected final Log Log = Log.getLog(getClass());

	private String source;
	private String dateFormatString = "yyyy-MM-dd'T'HH:mm:ss";
	private String defaultResourceName;
	List<Task> globalTasks = new ArrayList<Task>();
	ResourcePool resourcePool;
	int resourceGroupCount = 0;
	XMLEvent e;
	XMLEventReader r;
	StringBuffer indent = new StringBuffer();
	SimpleDateFormat sdf = new SimpleDateFormat(dateFormatString);

	public void execute(ServiceFactory connection) throws FileNotFoundException, XMLStreamException, CycleDetectedException, ChangeInvalidException,
			PersistenceReferenceException {
		this.connection = connection;
		importResults.start = new Date();
		XMLInputFactory factory = XMLInputFactory.newInstance();
		logger.info("import source=" + source);
		r = factory.createXMLEventReader(source, new FileInputStream(source));
		e = r.peek();

		while (!e.isStartDocument()) {
			e = r.nextEvent();
		}

		doDocument();

		importResults.finish = new Date();
		report();

	}

	/**
	 * Checks to see if there is a base calendar in the database and creates one if not. Attaches base calendar to all resources then stores
	 * the ResourcePool, and global tasks
	 * 
	 * @param oodb
	 */
	// public void persist(DatabaseConnection dao) {
	public void persist() {
		for (IndividualResource individual : resourcePool.getIndividuals()) {
			individual.setBaseCalendar(getBaseCalendar());
		}
		throw new RuntimeException("XMLImport.persist() incomplete ");
		// dao.getResourceDao().save(resourcePool);
		// dao.getTaskDao().save(globalTasks);
		// dao.commit();
	}

	private void report() {

		// TODO check for matched resources
		importResults.matchedResources = 0;

		double elapsed = importResults.finish.getTime() - importResults.start.getTime();
		double d = elapsed / 1000;

		logger.info("-----------Global resource list ------------");
		logger.info("-----------Groups-------------");
		for (ResourceGroup group : resourcePool.getGroups()) {
			logger.info(group.getName());
		}
		logger.info("-----------Individuals-------------");
		for (IndividualResource individual : resourcePool.getIndividuals()) {
			logger.info(individual.getName());
		}

		// now do a proper count of tasks
		importResults.projects = 0;
		importResults.tasks = 0;
		importResults.summaryTasks = 0;

		for (Task task : globalTasks) {
			// Class<? extends Task> clazz = task.getClass();
			// if (clazz == BaseTask.class) {
			// importResults.tasks++;
			// } else {
			// if (clazz == ProjectAbstractTask.class) {
			// importResults.summaryTasks++;
			// } else {
			// if (clazz == Project.class) {
			// importResults.projects++;
			// }
			// }
			// }
			if (task.isProject()) {
				importResults.projects++;
			} else {
				if (task.hasSubTasks()) {
					importResults.summaryTasks++;
				} else {
					importResults.tasks++;
				}
			}

		}

		importResults.source = source;

		// TODO these should compare start count with plannedFinish count, this will be
		// done against an
		// existing pool eventually
		importResults.newResources = resourcePool.individualCount();
		importResults.newResourceGroups = resourcePool.groupCount();

		logger.info("import from: " + importResults.source);
		logger.info("started at: " + importResults.start);
		logger.info("time taken: " + d + " seconds");
		logger.info("total errors: " + (importResults.taskUIDNotFound + importResults.predecessorNotFound));
		logger.info("task ref not found: " + importResults.taskUIDNotFound);
		logger.info("predecessor not found: " + importResults.predecessorNotFound);
		logger.info("projects: " + importResults.projects);
		logger.info("summary tasks " + importResults.summaryTasks);
		logger.info("tasks: " + importResults.tasks);
		logger.info("project lines: " + (globalTasks.size()));
		logger.info("new resources: " + importResults.newResources);
		logger.info("new resource groups: " + importResults.newResourceGroups);
		logger.info("resources matching existing: " + "not yet implemented");// importResults
		// .
		// matchedResources
		// );

	}

	private void doDocument() throws XMLStreamException, CycleDetectedException, ChangeInvalidException, PersistenceReferenceException {
		EnumSet<Tag> thisSet = EnumSet.of(Tag.Project);
		logger.info("Document");
		boolean done = false;
		while (!done) {
			Tag tag = nextTag(thisSet);
			switch (tag) {

			case Project:
				doProject(null);
				break;

			case _End:
				done = true;
				break;
			}
		}

	}

	private void doProject(Task parent) throws XMLStreamException, CycleDetectedException, ChangeInvalidException, PersistenceReferenceException {

		Stack<PredecessorReference> predecessorReferences = new Stack<PredecessorReference>();
		Map<String, Resource> projectResources = new HashMap<String, Resource>();
		ArrayList<Assignment> assignments = new ArrayList<Assignment>();
		ArrayList<TaskOutline> planOrder = new ArrayList<TaskOutline>();

		final Tag thisTag = Tag.Project;
		EnumSet<Tag> thisSet = EnumSet.of(Tag.Assignments, Tag.Name, Tag.Project, Tag.Resources, Tag.Tasks);
		BaseTask project;
		if (parent == null) {
			project = new BaseTask(connection);
		} else {
			project = (BaseTask) parent;
		}
		currentProject = project;
		planOrder.add(new TaskOutline(project, 0));

		tagStack.push(thisTag);
		logTaskStart(thisTag, parent);
		importResults.projects++;

		boolean done = false;
		while (!done) {

			Tag tag = nextTag(thisSet);

			switch (tag) {
			case Assignments:
				doAssignments(assignments);
				break;

			case Name:
				project.setName(getElementData());
				logAttribute(tag, project.getName());
				break;
			//
			// case Project:
			// doProject(project);
			// break;

			case Resources:
				doResources(projectResources);
				break;

			case Tasks:
				doTasks(project, predecessorReferences, planOrder);
				break;

			case _End:
				done = true;
				tagStack.pop();
				logTagEnd(thisTag);

			default:
				break;
			}
		}
		doStructure(planOrder, predecessorReferences, projectResources, assignments);
	}

	private void doAssignment(ArrayList<Assignment> assignments) throws NumberFormatException, XMLStreamException {
		final Tag thisTag = Tag.Assignment;
		EnumSet<Tag> thisSet = EnumSet.of(Tag.ResourceUID, Tag.TaskUID, Tag.Units);
		Assignment ar = new Assignment();

		tagStack.push(thisTag);
		logTagStart(thisTag);

		boolean done = false;
		while (!done) {

			Tag tag = nextTag(thisSet);

			switch (tag) {

			case ResourceUID:
				ar.resourceUID = getElementData();
				logAttribute(tag, ar.resourceUID);
				break;

			case TaskUID:
				ar.taskUID = getElementData();
				logAttribute(tag, ar.taskUID);
				break;

			case Units:
				ar.units = Double.parseDouble(getElementData());
				logAttribute(tag, Double.toString(ar.units));
				break;

			case _End:
				assignments.add(ar);
				done = true;
				tagStack.pop();
				logTagEnd(thisTag);

			default:
				break;
			}
		}

	}

	private void doPredecessorLink(Task task, Stack<PredecessorReference> predecessorReferences) throws XMLStreamException {
		final Tag thisTag = Tag.PredecessorLink;
		EnumSet<Tag> thisSet = EnumSet.of(Tag.PredecessorUID, Tag.UID);
		PredecessorReference pref = new PredecessorReference();
		predecessorReferences.add(pref);
		pref.task = task;
		tagStack.push(thisTag);
		logTagStart(thisTag);

		boolean done = false;
		while (!done) {

			Tag tag = nextTag(thisSet);

			switch (tag) {
			case PredecessorUID:
				pref.referenceUID = getElementData();
				logAttribute(tag, pref.referenceUID);
				break;

			case _End:
				done = true;
				tagStack.pop();
				logTagEnd(thisTag);

			default:
				break;
			}
		}
	}

	/**
	 * Task ids are unique only within the scope of a project so references have to be resolved within the scope of the project. Once
	 * {@link #resolveDependencies predecessors resolved} and assignments made, project tasks can be moved to the global list. Note that
	 * projects would be duplicated if they were added to the global list when they are sub-projects within the purged order list. This is
	 * because they are added already when they are in the project list. To avoid this, a duplicate check is carried out.
	 * 
	 * @see #allocateAssignments
	 * @see #removeAbstractTask0()
	 * @see #resolveDependencies
	 */
	private void moveProjectToGlobal(ArrayList<TaskOutline> purgedOrder) {
		for (TaskOutline tout : purgedOrder) {

			if (!globalTasks.contains(tout.task)) {
				globalTasks.add(tout.task);
				logger.info("Moving " + tout.task.getClass()
						.getSimpleName() + "  " + tout.task.qualifiedName());
			}
		}

	}

	/**
	 * Matches assignments to resources and tasks
	 * 
	 * @throws PersistenceReferenceException
	 */
	private void allocateAssignments(ArrayList<Assignment> assignments, Map<String, Task> AbstractTasks, Map<String, Resource> projectResources)
			throws PersistenceReferenceException {

		for (Assignment assignment : assignments) {
			Task task = AbstractTasks.get(assignment.taskUID);

			// this is done first because there may be an assignment associated
			// with
			// it - usually though there is no resource attached to it, but
			// better
			// logging would be good.
			// TODO improve logging
			if (task == null) {
				logger.warn("taskUID " + assignment.taskUID + " not found");
				importResults.taskUIDNotFound++;
				return;
			}

			Resource projectResource = projectResources.get(assignment.resourceUID);
			String resourceName = null;
			// but does this resource (by name) already exist in the pool?

			if (projectResource == null) {
				logger.warn("no resource assigned to " + task.qualifiedName() + ", using default");
				importResults.noResource++;
				resourceName = getDefaultResourceName();
			} else {
				resourceName = projectResource.getName();
			}

			// try getting global resource from the pool
			Resource globalResource;
			try {
				globalResource = resourcePool.getResource(resourceName);
			}
			// if not use project resource, but add it to the pool as well
			catch (Exception e) {
				logger.error("Condition needs fixing");
				// resourcePool.addResource(projectResource);
				globalResource = projectResource;
			}
			// if ther've been no previous assignments for this task (each
			// assignment
			// is separate in the XML file), just assign the resource
			// if there has been an earlier assignment, create a group made up
			// of
			// assigned resources plus the
			// global resource which was to be assigned (which could also be a
			// group)
			// resource assigned to summary tasks are ignored but logged

			if (task instanceof BaseTask) {
				BaseTask rt = (BaseTask) task;
				Resource assignedResource = rt.getAssignedResource();
				if (assignedResource == null) {
					rt.assignResource(globalResource, assignment.units);
				} else {
					// build list of all individual resources assigned to this
					// task
					ArrayList<IndividualResource> builder = new ArrayList<IndividualResource>();
					assignedResource.getIndividualResources(builder);
					globalResource.getIndividualResources(builder);
					// get or create a group containing just these resources,
					// and
					// assign
					// it
					try {
						rt.assignResource(resourcePool.groupContainingOnly(builder), assignment.units);
					} catch (PersistenceReferenceException e) {
						logger.error("Do seomthing with this");
						e.printStackTrace();
					}
				}

			}
		}// if instance of atomic task

		// finally, go through all the tasks and if it is atomic and has no
		// resource
		// assignment set it to default resource (as defined by the pool) and
		// units
		// of 1
		Resource resource = resourcePool.defaultResource();
		for (Task task : AbstractTasks.values()) {
			if (task instanceof BaseTask) {
				BaseTask rt = (BaseTask) task;
				if (rt.getAssignedResource() == null) {
					rt.assignResource(resource, 1);
				}
			}// if atomic
		}
	}

	/**
	 * Builds predecessor and successor links from imported predecessor information
	 * 
	 * @throws PersistenceReferenceException
	 */
	private void resolveDependencies(Stack<PredecessorReference> predecessorReferences, Map<String, Task> AbstractTasks)
			throws PersistenceReferenceException {
		while (predecessorReferences.size() > 0) {
			PredecessorReference pref = predecessorReferences.pop();
			Task successor = pref.task;
			Task pred = AbstractTasks.get(pref.referenceUID);
			BaseTask predecessor = (pred instanceof BaseTask ? (BaseTask) pred : null);
			if (predecessor == null) {
				logger.warn("predecessor " + pref.referenceUID + " not found");
				importResults.predecessorNotFound++;
			}
			successor.addPredecessor(predecessor);
		}

	}

	private void doResources(Map<String, Resource> projectResources) throws XMLStreamException {
		final Tag thisTag = Tag.Resources;
		EnumSet<Tag> thisSet = EnumSet.of(Tag.Resource, Tag._End);
		tagStack.push(thisTag);
		logTagStart(thisTag);
		boolean done = false;
		while (!done) {

			Tag tag = nextTag(thisSet);
			switch (tag) {

			case Resource:
				doResource(projectResources);
				break;

			case _End:
				done = true;
				tagStack.pop();
				logTagEnd(thisTag);

			default:
				break;
			}
		}
	}

	private void doResource(Map<String, Resource> projectResources) throws XMLStreamException {
		final Tag thisTag = Tag.Resource;
		EnumSet<Tag> thisSet = EnumSet.of(Tag.Name, Tag.UID, Tag._End);
		tagStack.push(thisTag);
		Resource resource = new IndividualResource(connection);
		logTagStart(thisTag);

		boolean done = false;
		while (!done) {

			Tag tag = nextTag(thisSet);

			switch (tag) {

			case Name:
				resource.setName(getElementData());
				logAttribute(tag, resource.getName());
				break;

			case UID:
				String rid = getElementData();
				projectResources.put(rid, resource);
				logAttribute(tag, rid);
				break;

			case _End:
				done = true;
				tagStack.pop();
				logTagEnd(thisTag);

			default:
				break;
			}
		}

	}

	private void logTaskStart(Tag element, Task task) {
		incIndent();
		String extra;
		if (task == null) {
			extra = "null parent";
		} else {
			extra = "parent=" + task.qualifiedName();
		}
		logger.info(indent.toString() + "<" + element + ">" + "(" + extra + ")");

	}

	private void logTagStart(Tag element) {
		incIndent();
		logger.info(indent.toString() + "<" + element + ">");
	}

	private void logTagEnd(Tag element) {

		logger.info(indent.toString() + "</" + element + ">");
		decIndent();
	}

	private void incIndent() {
		indent.append("  ");
	}

	private void decIndent() {
		indent.delete(0, 2);
	}

	private void doAssignments(ArrayList<Assignment> assignments) throws XMLStreamException {
		final Tag thisTag = Tag.Assignments;
		EnumSet<Tag> thisSet = EnumSet.of(Tag.Assignment);
		tagStack.push(thisTag);
		logTagStart(thisTag);
		boolean done = false;
		while (!done) {

			Tag tag = nextTag(thisSet);
			switch (tag) {

			case Assignment:

				doAssignment(assignments);
				break;

			case _End:
				done = true;
				tagStack.pop();
				logTagEnd(thisTag);

			default:
				break;
			}
		}
	}

	private void doTasks(BaseTask project, Stack<PredecessorReference> predecessorReferences, ArrayList<TaskOutline> planOrder)
			throws XMLStreamException, CycleDetectedException, ChangeInvalidException, NumberFormatException, PersistenceReferenceException {
		final Tag thisTag = Tag.Tasks;
		EnumSet<Tag> thisSet = EnumSet.of(Tag.Task);
		tagStack.push(thisTag);
		logTagStart(thisTag);
		int i = 0;
		boolean done = false;
		while (!done) {

			// Tag tag = nextTag(Tag.Project);
			Tag tag = nextTag(thisSet);
			switch (tag) {

			case Task:
				i++;
				doTask(project, predecessorReferences, planOrder);
				break;

			case _End:
				done = true;
				tagStack.pop();
				logTagEnd(thisTag);

			default:
				break;
			}
		}
	}

	private void doTask(BaseTask project, Stack<PredecessorReference> predecessorReferences, ArrayList<TaskOutline> planOrder)
			throws XMLStreamException, CycleDetectedException, ChangeInvalidException, NumberFormatException, PersistenceReferenceException {

		final Tag thisTag = Tag.Task;
		EnumSet<Tag> thisSet = EnumSet.of(Tag.Name, Tag.PredecessorLink, Tag.Priority, Tag.Project, Tag.StatusManager, Tag.Type, Tag.UID, Tag.Notes,
				Tag.OutlineLevel, Tag.Duration, Tag._End);
		tagStack.push(thisTag);
		BaseTask task = new BaseTask(connection);
		logTaskStart(thisTag, project);

		task.setParentTask(project);
		TaskOutline outline = null;
		importResults.tasks++;

		boolean done = false;
		while (!done) {

			// Tag tag = nextTag(Tag.Tasks);
			Tag tag = nextTag(thisSet);

			switch (tag) {

			case ActualStart:
				try {
					task.setActualStart(decodeDate(getElementData()));
				} catch (ParseException e) {
					logger.warn("Parse error in field " + tag + " of task " + task.qualifiedName() + " field set to null");
				}
				logAttribute(tag, encodeDate(task.getActualStart()));
				break;

			case ActualFinish:
				try {
					task.setActualFinish(decodeDate(getElementData()));
				} catch (ParseException e) {
					logger.warn("Parse error in field " + tag + " of task " + task.qualifiedName() + " field set to null");
				}
				logAttribute(tag, encodeDate(task.getActualFinish()));
				break;

			case Duration:
				int minutes = decodeTime(getElementData());
				double duration = getBaseCalendar().minutesToWorkingDays(minutes, fixedMonday.getTime());
				if (duration > 365) {
					duration = 365;
				}
				task.setDuration(duration);
				logAttribute(tag, Double.toString(task.getDuration()));
				break;

			case Name:
				task.setName(getElementData());
				logAttribute(tag, task.getName());
				break;

			case Notes:
				task.setSpecification(getElementData());
				logAttribute(tag, task.getName());
				break;

			case OutlineLevel:
				int outlineLevel = Integer.parseInt(getElementData());
				logAttribute(tag, Integer.toString(outlineLevel));
				outline = new TaskOutline(task, outlineLevel);
				planOrder.add(outline);
				break;

			case PredecessorLink:
				doPredecessorLink(task, predecessorReferences);
				break;

			case Project:
				// because it is converted to a project
				outline.task.setProject();
				doProject(outline.task);
				break;

			case Priority:
				task.setPriority(Integer.parseInt(getElementData()));
				logAttribute(tag, Integer.toString(task.getPriority()));
				break;

			case RemainingWork:
				task.setWorkRemaining(decodeTime(getElementData()));
				logAttribute(tag, Integer.toString(task.getWorkRemaining()));
				break;

			case StatusManager:
				String pmName = getElementData();
				if (project.isProject()) {
					project.setProjectManager(pmName);
					logAttribute(tag, project.getProjectManager());
				}
				break;

			case Type:
				task.setFixedDuration(decodeTaskType(getElementData()));
				int code = (task.isFixedDuration() ? 1 : 0);
				logAttribute(tag, Integer.toString(code));
				break;

			case UID:
				task.setTempId(Integer.parseInt(getElementData()));
				logAttribute(tag, Integer.toString(task.getTempId()));
				break;

			case _End:
				done = true;
				tagStack.pop();
				logTagEnd(thisTag);

			default:
				break;
			}
		}

	}

	private String encodeDate(Date date) {
		return sdf.format(date);
	}

	private Date decodeDate(String elementData) throws ParseException {
		return sdf.parse(elementData);
	}

	private void logAttribute(Tag attributeName, String value) {
		incIndent();
		logger.info(indent + "<" + attributeName + ">" + value + "</" + attributeName + ">");
		decIndent();
	}

	/**
	 * Returns an Tag representing either the next start element or the end element matching the one currently at the top of the stack.
	 * 
	 * @throws XMLStreamException
	 */
	private Tag nextTag(EnumSet<Tag> set) throws XMLStreamException {
		boolean done = false;
		Tag tag = null;
		while ((!done) && (r.peek() != null)) {
			e = r.nextEvent();
			if (e.isStartElement()) {
				String tagname = e.asStartElement()
						.getName()
						.getLocalPart();
				tag = convertToTag(tagname);
				if (!set.contains(tag)) {
					goToElementEnd(tagname);
					done = false;
				} else {
					done = true;
				}

			}
			if (e.isEndElement()) {
				tag = convertToTag(e.asEndElement()
						.getName()
						.getLocalPart());
				done = (tag.equals(tagStack.peek()));
				if (done) {
					tag = Tag._End;
				}
			}
			if (e.isEndDocument()) {
				tag = Tag._End;
				done = true;
			}
		}

		return tag;
	}

	private Tag convertToTag(String localPart) {
		try {
			return Tag.valueOf(localPart);
		} catch (Exception e) {
			return Tag._Unused;
		}
	}

	private void goToElementEnd(String target) throws XMLStreamException {
		boolean foundElementStart = false;
		while (!foundElementStart) {
			if (e.getEventType() == XMLEvent.END_ELEMENT) {
				String currentElementName = e.asEndElement()
						.getName()
						.getLocalPart();
				if (currentElementName.equals(target)) {
					foundElementStart = true;

				} else {
					e = r.nextEvent();
				}
			} else {
				e = r.nextEvent();
			}
		}

	}

	public void setSource(String source) {
		this.source = source;

	}

	/**
	 * Assumes already at start element
	 * 
	 * @param elementName
	 * @return
	 * @throws XMLStreamException
	 */

	private String getElementData() throws XMLStreamException {
		goToCharacters();
		return e.asCharacters()
				.getData();

	}

	private void goToCharacters() throws XMLStreamException {
		while ((e.getEventType() != XMLEvent.CHARACTERS) && (e.getEventType() != XMLEvent.END_ELEMENT)) {
			e = r.nextEvent();
		}

	}

	/**
	 * 1 is fixed duration
	 * 
	 * @param type
	 * @return
	 */
	private boolean decodeTaskType(String type) {
		int tasktype = Integer.parseInt(type);
		return (tasktype == 1);

	}

	/**
	 * Decodes the time format used by MS Project and returns it in minutes
	 * 
	 * @param text
	 * @return
	 */
	public int decodeTime(String text) {
		int hourStart = 2;
		int hourEnd = text.indexOf('H');
		int minuteStart = hourEnd + 1;
		int minuteEnd = text.indexOf('M');
		int hours = Integer.parseInt(text.substring(hourStart, hourEnd));
		int minutes = Integer.parseInt(text.substring(minuteStart, minuteEnd));
		return (hours * 60) + minutes;
	}

	public String getDateFormatString() {
		return dateFormatString;
	}

	public void setDateFormatString(String dateFormatString) {
		this.dateFormatString = dateFormatString;
		sdf = new SimpleDateFormat(dateFormatString);
	}

	private void doStructure(ArrayList<TaskOutline> planOrder, Stack<PredecessorReference> predecessorReferences,
			Map<String, Resource> projectResources, ArrayList<Assignment> assignments) throws CycleDetectedException, ChangeInvalidException,
			PersistenceReferenceException {

		// Stack<TaskOutline> hierarchy = new Stack<TaskOutline>();
		Map<String, Task> AbstractTaskMap = new HashMap<String, Task>();
		ArrayList<TaskOutline> purgedOrder = new ArrayList<TaskOutline>();

		/**
		 * There are "blank" tasks which are the first task of a project and have an id==0 - these are part of the project definition itself
		 * and therefore duplicates to be removed. When sub-projects are processed there are also some tasks which come up with null names -
		 * don't know why but they seem redundant These are removed as the data is transferred to stack and map for further processing
		 */
		for (TaskOutline tout : planOrder) {
			boolean keep = true;
			Task task = tout.task;

			if (task.getName() == null) {
				logger.info("purging " + task.qualifiedName());
				// task.purge();
				keep = false;
			}
			// purge if TempId==0, unless its a project
			if (task.getTempId() == 0) {
				if (!(task.isProject())) {
					logger.info("purging " + task.qualifiedName());
					// task.purge();
					keep = false;
				}
			}

			if (keep) {
				AbstractTaskMap.put(Integer.toString(task.getTempId()), task);
				purgedOrder.add(tout);
			}
		}

		allocateAssignments(assignments, AbstractTaskMap, projectResources);
		allocateMissingResource(AbstractTaskMap, projectResources);
		resolveDependencies(predecessorReferences, AbstractTaskMap);

		String tab = "\t";
		String tab3 = "\t\t\t";
		logger.info(">>>>>>>>>>>>>>>>>>" + planOrder.size());
		for (TaskOutline tout : purgedOrder) {
			String parentName;
			if (tout.task.getParentTask() == null) {
				parentName = "null";
			} else {
				parentName = tout.task.getParentTask()
						.qualifiedName();
			}
			String predecessorInfo = (tout.task instanceof Task ? (tout.task).predecessors()
					.toString() : "no predeccsors, not an atomic task");
			logger.info(tout.task.getClass()
					.getSimpleName() + tab + tout.task.getName() + tab3 + "UID=" + tout.task.getTempId() + tab + "pty=" + tout.task.getPriority()
					+ tab + "parent=" + parentName + tab + "predecessors=" + predecessorInfo);

		}
		logger.info("---------------------------------------------------------");

		// use purged order now to build relationships
		planOrder = purgedOrder;

		TaskOutline topItem = planOrder.get(0);
		// make sure the top item is a project or support service
		// if it isn't it has all gone wrong
		if ((!topItem.task.isProject()) && (!topItem.task.isService())) {
			throw new ImportStructureException("Top level item must be a project or support service - import failed");
		}

		int currentOutlineLevel = 0;

		for (int i = 1; i < planOrder.size(); i++) {
			Task thisTask = planOrder.get(i).task;
			int outlineLevel = planOrder.get(i).outlineLevel;

			LevelChange levelChange = LevelChange.SAME;
			if (outlineLevel > currentOutlineLevel) {
				levelChange = LevelChange.DOWN;
				currentOutlineLevel = outlineLevel;
			} else {
				if (outlineLevel < currentOutlineLevel) {
					levelChange = LevelChange.UP;
					currentOutlineLevel = outlineLevel;
				}
			}

			Task prevTask = planOrder.get(i - 1).task;
			switch (levelChange) {
			case DOWN:
				planOrder.get(i - 1).task = prevTask;
				thisTask.setParentTask((BaseTask) prevTask);
				break;

			// if on the same level this is a sibling of previous task. Just use
			// the
			// same parent
			case SAME:
				thisTask.setParentTask(prevTask.getParentTask());
				break;
			// wind back up the list until you find a task at the same level as
			// this.
			// That will be a sibling, use its parent as this task's parent
			case UP:
				int j = i - 1;
				while (planOrder.get(j).outlineLevel != outlineLevel) {
					j--;
				}
				// j now pointing at sibling
				thisTask.setParentTask(planOrder.get(j).task.getParentTask());
				break;
			}
		}
		logger.info(">>>>>>>>>>>>>>>>>>");
		for (TaskOutline tout : purgedOrder) {
			String parentName;
			if (tout.task.getParentTask() == null) {
				parentName = "null";
			} else {
				parentName = tout.task.getParentTask()
						.getName();
			}

			String resourceInfo = (tout.task instanceof BaseTask ? ((BaseTask) tout.task).assignmentDescription()
					: "no resource assigned, task is not atomic");
			String predecessorInfo = (tout.task instanceof Task ? (tout.task).predecessors()
					.toString() : "no predecessors, task is not atomic");
			logger.info(tout.task.getClass()
					.getSimpleName() + tab + tout.task.getName() + tab3 + "UID=" + tout.task.getTempId() + tab + "pty=" + tout.task.getPriority()
					+ tab + "parent=" + parentName + tab + "predecessors=" + predecessorInfo + resourceInfo);

		}
		logger.info("---------Resources--------------");

		for (Map.Entry<String, Resource> entry : projectResources.entrySet()) {
			logger.info(entry.getKey() + ":" + entry.getValue());
		}
		logger.info("<<<<<<<<<<<<<<<<<<<<<<<");

		moveProjectToGlobal(purgedOrder);

	}

	/**
	 * If there were no assignments for a task, it will have no resource assigned. This method allocates the nominated default resource to
	 * tasks without resource, with assignment units of 1. This does rely on there being a defined default resource in the resource pool
	 * 
	 * @param AbstractTaskMap
	 * @param projectResources
	 * @throws PersistenceReferenceException
	 */
	private void allocateMissingResource(Map<String, Task> AbstractTaskMap, Map<String, Resource> projectResources)
			throws PersistenceReferenceException {
		Resource defaultResource = resourcePool.defaultResource();
		for (Map.Entry<String, Task> entry : AbstractTaskMap.entrySet()) {
			Task task = entry.getValue();
			if (task instanceof BaseTask) {
				BaseTask rt = (BaseTask) task;
				if (rt.getAssignedResource() == null) {
					rt.assignResource(defaultResource, 1);
				}
			}

		}

	}

	public String getDefaultResourceName() {
		return defaultResourceName;
	}

	public void setDefaultResourceName(String defaultResourceName) {
		if (resourcePool == null) {
			throw new InitialisationException("ResourcePool not set");
		}
		this.defaultResourceName = defaultResourceName;
		resourcePool.setDefaultResourceName(defaultResourceName);
	}

	public ResourcePool getResourcePool() {
		return resourcePool;
	}

	public void setResourcePool(ResourcePool resourcePool) {
		this.resourcePool = resourcePool;
	}

	public List<Task> globalTasks() {
		return globalTasks;
	}

	/**
	 * // outline level 0 is the task associated with the project itself // if top level just push on to stack if (outlineLevel == 0) {
	 * hierarchy.push(tout); } else {
	 * 
	 * TaskOutline toThis = new TaskOutline(task, outlineLevel); TaskOutline toLast = hierarchy.peek(); // sibling if (toThis.outlineLevel
	 * == toLast.outlineLevel) { task.setParentTask(toLast.task.getParentTask()); hierarchy.push(toThis); } // child if (toThis.outlineLevel
	 * > toLast.outlineLevel) { BaseTask st; if (toLast.task instanceof Task) { Task at = (Task) toLast.task; if (at.hasDependencies())
	 * { // cast should be safe but relies on logic not converting // toThis.task to a summary before now at.moveDependenciesTo((Task)
	 * toThis.task); } st = Util.toAbstractTask(at); toLast.task = st; } else { st = (BaseTask) toLast.task; } task.setParentTask(st);
	 * hierarchy.push(toThis); } // back up the hierarchy if (toThis.outlineLevel < toLast.outlineLevel) { // up any number of levels while
	 * (toThis.outlineLevel < toLast.outlineLevel) { hierarchy.pop(); toLast = hierarchy.peek(); } BaseTask st; if (toLast.task
	 * instanceof Task) { Task at = (Task) toLast.task; st = Util.toAbstractTask(at); toLast.task = st; } else { st = (BaseTask)
	 * toLast.task; } task.setParentTask(st); hierarchy.push(toThis);
	 * 
	 * break; } }
	 */
}